#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "gc.h"
#include "mem.h"
#include "eval.h"
#include "rarray.h"
#include "bytecode.h"
#include "redintobject.h"
#include "redmapobject.h"
#include "redboolobject.h"
#include "redcodeobject.h"
#include "redarrayobject.h"
#include "redblockobject.h"
#include "redfloatobject.h"
#include "redframeobject.h"
#include "redtupleobject.h"
#include "redstringobject.h"
#include "redfunctionobject.h"
#include "redexceptionobject.h"

static int recursive = 0;

static inline const char *unary_operator(opcode_t opcode)
{
	switch (opcode)
	{
		case POS:			return "__pos__";
		case NEG:			return "__neg__";
		case BIT_NOT:		return "__bit_not__";
		case BOOL_NOT:		return "__bool_not__";
		default:			return NULL;
	}
}

static inline const char *binary_operator(opcode_t opcode)
{
	switch (opcode)
	{
		case ADD:			return "__add__";
		case SUB:			return "__sub__";
		case MUL:			return "__mul__";
		case DIV:			return "__div__";
		case MOD:			return "__mod__";
		case POW:			return "__pow__";
		case BIT_OR:		return "__bit_or__";
		case BIT_AND:		return "__bit_and__";
		case BIT_XOR:		return "__bit_xor__";
		case LSHIFT:		return "__lshift__";
		case RSHIFT:		return "__rshift__";
		case AUG_ADD:		return "__aug_add__";
		case AUG_SUB:		return "__aug_sub__";
		case AUG_MUL:		return "__aug_mul__";
		case AUG_DIV:		return "__aug_div__";
		case AUG_MOD:		return "__aug_mod__";
		case AUG_POW:		return "__aug_pow__";
		case AUG_BIT_OR:	return "__aug_bit_or__";
		case AUG_BIT_AND:	return "__aug_bit_and__";
		case AUG_BIT_XOR:	return "__aug_bit_xor__";
		case AUG_LSHIFT:	return "__aug_lshift__";
		case AUG_RSHIFT:	return "__aug_rshift__";
		case BOOL_OR:		return "__bool_or__";
		case BOOL_AND:		return "__bool_and__";
		case BOOL_XOR:		return "__bool_xor__";
		case EQ:			return "__eq__";
		case LE:			return "__le__";
		case GE:			return "__ge__";
		case NEQ:			return "__neq__";
		case LEQ:			return "__leq__";
		case GEQ:			return "__geq__";
		default:			return NULL;
	}
}

static inline RedObject *resolve_name(RedFrameObject *frame, RedObject *name)
{
	RedObject *value = NULL;
	RedFrameObject *current = frame;

	while (!value && current)
	{
		value = RedMap_GetString(current->locals, name);
		current = (RedFrameObject *)(current->closure);
	}

	return value;
}

RedObject *RedEval_ExecFrame(RedObject *frame)
{
	RedFrameObject *self = (RedFrameObject *)frame;

	/* limit the recursive depth
	 * this exception must be thrown to parent
	 */
	if (ATOMIC_INC(recursive) > FRAME_RECURSIVE_MAX)
	{
		ATOMIC_DEC(recursive);
		RedException_ThrowFormat(&RedExc_RecursionTooDepthError, "Recursion too depth");
		RedException_Rewind(frame);
		return NULL;
	}

/* pop the stack top */
#define POP()											\
	({													\
		if (self->sp <= 0)								\
			goto stack_corrupt;							\
		self->stack[--self->sp];						\
	})

/* get the n-th item from stack (counting from stack-top) */
#define GET(i)											\
	({													\
		if (self->sp < (i))								\
			goto stack_corrupt;							\
		self->stack[self->sp - (i) - 1];				\
	})

/* decrese stack pointer by `n`, and deref these objects */
#define DEC(n)											\
	do													\
	{													\
		int _n = (n);									\
		while (_n--)									\
		{												\
			if (--self->sp < 0)							\
				goto stack_corrupt;						\
			Red_UNREF(self->stack[self->sp]);			\
		}												\
	} while (0)

/* replace stack top by RedObject `o` */
#define SET(o)											\
	do													\
	{													\
		if (self->sp <= 0)								\
			goto stack_corrupt;							\
		Red_REF(o);										\
		Red_UNREF(self->stack[self->sp - 1]);			\
		self->stack[self->sp - 1] = (o);				\
	} while (0)

/* push RedObject `o` into stack */
#define PUSH(o)											\
	do													\
	{													\
		if (self->sp == self->capacity)					\
		{												\
			self->capacity <<= 1;						\
			self->stack = RedMem_Realloc(				\
				self->stack,							\
				sizeof(RedObject *) * self->capacity	\
			);											\
		}												\
		Red_REF(o);										\
		self->stack[self->sp++] = (o);					\
	} while (0)

/* rewind stack until the stack pointer (sp) before entering `try` block */
#define REWIND(sp)										\
	do													\
	{													\
		if (self->sp < (sp))							\
			goto stack_corrupt;							\
		for (int i = (sp); i < self->sp; i++)			\
			Red_UNREF(self->stack[i]);					\
		self->sp = (sp);								\
	} while (0)

	/* fetch meta data */
	register int pb = self->blocks.pb;
	register int count = RedCode_Size(self->code);
	register bytecode_t *buffer = RedCode_Buffer(self->code);

	/* eval each bytecode */
	while (self->pc < count)
	{
		/* VM registers */
		register RedObject *a = NULL;
		register RedObject *b = NULL;
		register RedObject *c = NULL;
		register RedObject *r = NULL;
		register RedObject *exc = NULL;

		/* fetch a bytecode from buffer */
		register bytecode_t *bytecode = &(buffer[self->pc++]);

		/* extract opcode and argument */
		register opcode_t	opcode = bytecode->opcode;
		register int		offset = (opcode & OP_OFFSET) ? bytecode->at_offset : 0;
		register RedObject *object = (opcode & OP_OBJECT) ? bytecode->at_object : NULL;

		/* main switch on `opcode` */
		switch (opcode)
		{
			/* load const into stack */
			case LOAD_CONST:
			{
				PUSH(object);
				break;
			}

			/* load object into stack */
			case LOAD_OBJECT:
			{
				if ((r = resolve_name(self, object)))
				{
					PUSH(r);
					break;
				}

				RedException_ThrowFormat(&RedExc_NameError, "Name '%s' not found", RedString_Buffer(object));
				goto exception;
			}

			case LOAD_ARG:		/* load obejct as argument */
			case STOR_OBJECT:	/* store object into locals */
			{
				a = POP();
				RedMap_PutString(self->locals, object, a);
				Red_UNREF(a);
				break;
			}

			/* remove object from locals, global objects are not removable */
			case DEL_OBJECT:
			{
				if (RedMap_DelString(self->locals, object))
					break;

				RedException_ThrowFormat(&RedExc_NameError, "Name '%s' not found", RedString_Buffer(object));
				goto exception;
			}

			/* get attribute */
			case GET_ATTR:
			{
				a = GET(0);
				r = RedObject_GetAttr(a, object);

				if (!r)
				{
					DEC(1);
					goto exception;
				}

				SET(r);
				Red_UNREF(r);
				break;
			}

			/* set attribute for object */
			case SET_ATTR:
			{
				a = GET(1);
				b = GET(0);
				r = RedObject_SetAttr(a, object, b);

				DEC(2);

				if (!r)
					goto exception;

				Red_UNREF(r);
				break;
			}

			/* remove attribute from object */
			case DEL_ATTR:
			{
				a = POP();
				r = RedObject_DelAttr(a, object);

				Red_UNREF(a);

				if (!r)
					goto exception;

				Red_UNREF(r);
				break;
			}

			/* get item for object */
			case GET_ITEM:
			{
				a = GET(1);
				b = GET(0);
				r = RedObject_GetItem(a, b);

				if (!r)
				{
					DEC(2);
					goto exception;
				}

				DEC(1);
				SET(r);
				Red_UNREF(r);
				break;
			}

			/* set item for object */
			case SET_ITEM:
			{
				a = GET(2);
				b = GET(1);
				c = GET(0);
				r = RedObject_SetItem(a, b, c);

				DEC(3);

				if (!r)
					goto exception;

				Red_UNREF(r);
				break;
			}

			/* remove item from object */
			case DEL_ITEM:
			{
				a = GET(1);
				b = GET(0);
				r = RedObject_DelItem(a, b);

				DEC(2);

				if (!r)
					goto exception;

				Red_UNREF(r);
				break;
			}

			/* invoke object as function */
			case INVOKE:
			{
				a = GET(0);
				b = RedTuple_FromLength(offset);

				for (int i = 0; i < offset; i++)
				{
					RedTuple_Items(b)[i] = GET(i + 1);
					Red_REF(RedTuple_Items(b)[i]);
				}

				r = RedObject_Invoke(a, b);

				DEC(offset);
				Red_UNREF(b);

				if (!r)
				{
					DEC(1);
					goto exception;
				}

				SET(r);
				Red_UNREF(r);
				break;
			}

			case POP_BLOCK:		/* pop rescure block */
			case POP_RETURN:	/* return from function */
			{
				if (opcode == POP_RETURN)
					r = POP();

				if (self->blocks.pb > pb)
				{
					int sp = self->blocks.ssp[--self->blocks.pb];
					RedObject *block = self->blocks.blocks[self->blocks.pb];

					REWIND(sp);
					Red_UNREF(block);
				}

				if (opcode == POP_RETURN)
				{
					ATOMIC_DEC(recursive);
					RedException_Clear();
					return r;
				}
				else if (RedException_Exception())
				{
					/* if exceptions still there, throw to parents */
					ATOMIC_DEC(recursive);
					RedException_Rewind(frame);
					return NULL;
				}

				break;
			}

			/* unary operators */
			case POS:
			case NEG:
			case BIT_NOT:
			case BOOL_NOT:
			{
				a = GET(0);
				tp_unary_fn_t func = NULL;

				switch (opcode)
				{
					case POS:		func = a->ob_type->tp_pos;		break;
					case NEG:		func = a->ob_type->tp_neg;		break;
					case BIT_NOT:	func = a->ob_type->tp_bit_not;	break;
					case BOOL_NOT:	func = a->ob_type->tp_bool_not;	break;

					/* never happens */
					default:
					{
						DEC(1);
						goto internal_error;
					}
				}

				if (func)
					r = func(a);
				else
					r = RedObject_InvokeMethodVa(a, unary_operator(opcode), 0);

				if (!r)
				{
					DEC(1);
					goto exception;
				}

				SET(r);
				Red_UNREF(r);
				break;
			}

			/* binary operators */
			case ADD:
			case SUB:
			case MUL:
			case DIV:
			case MOD:
			case POW:
			case BIT_OR:
			case BIT_AND:
			case BIT_XOR:
			case LSHIFT:
			case RSHIFT:
			case AUG_ADD:
			case AUG_SUB:
			case AUG_MUL:
			case AUG_DIV:
			case AUG_MOD:
			case AUG_POW:
			case AUG_BIT_OR:
			case AUG_BIT_AND:
			case AUG_BIT_XOR:
			case AUG_LSHIFT:
			case AUG_RSHIFT:
			case BOOL_OR:
			case BOOL_AND:
			case BOOL_XOR:
			{
				a = GET(1);
				b = GET(0);
				tp_binary_fn_t func = NULL;

				switch (opcode)
				{
					case ADD:			func = a->ob_type->tp_add;			break;
					case SUB:			func = a->ob_type->tp_sub;			break;
					case MUL:			func = a->ob_type->tp_mul;			break;
					case DIV:			func = a->ob_type->tp_div;			break;
					case MOD:			func = a->ob_type->tp_mod;			break;
					case POW:			func = a->ob_type->tp_pow;			break;
					case BIT_OR:		func = a->ob_type->tp_bit_or;		break;
					case BIT_AND:		func = a->ob_type->tp_bit_and;		break;
					case BIT_XOR:		func = a->ob_type->tp_bit_xor;		break;
					case LSHIFT:		func = a->ob_type->tp_lshift;		break;
					case RSHIFT:		func = a->ob_type->tp_rshift;		break;
					case AUG_ADD:		func = a->ob_type->tp_aug_add;		break;
					case AUG_SUB:		func = a->ob_type->tp_aug_sub;		break;
					case AUG_MUL:		func = a->ob_type->tp_aug_mul;		break;
					case AUG_DIV:		func = a->ob_type->tp_aug_div;		break;
					case AUG_MOD:		func = a->ob_type->tp_aug_mod;		break;
					case AUG_POW:		func = a->ob_type->tp_aug_pow;		break;
					case AUG_BIT_OR:	func = a->ob_type->tp_aug_or;		break;
					case AUG_BIT_AND:	func = a->ob_type->tp_aug_and;		break;
					case AUG_BIT_XOR:	func = a->ob_type->tp_aug_xor;		break;
					case AUG_LSHIFT:	func = a->ob_type->tp_aug_lshift;	break;
					case AUG_RSHIFT:	func = a->ob_type->tp_aug_rshift;	break;
					case BOOL_OR:		func = a->ob_type->tp_bool_or;		break;
					case BOOL_AND:		func = a->ob_type->tp_bool_and;		break;
					case BOOL_XOR:		func = a->ob_type->tp_bool_xor;		break;

					/* never happens */
					default:
					{
						DEC(2);
						goto internal_error;
					}
				}

				if (func)
					r = func(a, b);
				else
					r = RedObject_InvokeMethodVa(a, binary_operator(opcode), 1, b);

				if (!r)
				{
					DEC(2);
					goto exception;
				}

				DEC(1);
				SET(r);
				Red_UNREF(r);
				break;
			}

			/* comparision operators */
			case EQ:
			case LE:
			case GE:
			case NEQ:
			case LEQ:
			case GEQ:
			{
				a = GET(1);
				b = GET(0);
				tp_binary_fn_t func = NULL;

				switch (opcode)
				{
					case EQ:	func = a->ob_type->tp_eq;	break;
					case LE:	func = a->ob_type->tp_le;	break;
					case GE:	func = a->ob_type->tp_ge;	break;
					case NEQ:	func = a->ob_type->tp_neq;	break;
					case LEQ:	func = a->ob_type->tp_leq;	break;
					case GEQ:	func = a->ob_type->tp_geq;	break;

					/* never happens */
					default:
					{
						DEC(2);
						goto internal_error;
					}
				}

				if (func)
					r = func(a, b);
				else
					r = RedObject_InvokeMethodVa(a, binary_operator(opcode), 1, b);

				if (r)
				{
					DEC(1);
					SET(r);
					Red_UNREF(r);
					break;
				}

				/* if special comparation method failed, try generic `__compare__` */
				r = RedObject_Compare(a, b);

				if (!r)
				{
					DEC(2);
					goto exception;
				}

				if (!RedInt_Exact(r))
				{
					DEC(2);
					RedException_ThrowFormat(&RedExc_TypeError, "An integer is required for '__compare__'");
					goto exception;
				}

				int64_t cmp = RedInt_Value(r);
				Red_UNREF(r);

				switch (opcode)
				{
					case EQ:  r = (cmp == 0 ? Red_True : Red_False); break;
					case LE:  r = (cmp <  0 ? Red_True : Red_False); break;
					case GE:  r = (cmp >  0 ? Red_True : Red_False); break;
					case NEQ: r = (cmp != 0 ? Red_True : Red_False); break;
					case LEQ: r = (cmp <= 0 ? Red_True : Red_False); break;
					case GEQ: r = (cmp >= 0 ? Red_True : Red_False); break;

					/* never happens */
					default:
					{
						DEC(2);
						goto internal_error;
					}
				}

				DEC(1);
				SET(r);
				break;
			}

			/* duplicate stack top */
			case DUP:
			{
				a = GET(0);
				PUSH(a);
				break;
			}

			/* duplicate stack top and item below stack top */
			case DUP2:
			{
				a = GET(0);
				b = GET(1);
				PUSH(a);
				PUSH(b);
				break;
			}

			/* discard stack top */
			case DROP:
			{
				a = POP();
				Red_UNREF(a);
				break;
			}

			/* branch immediately */
			case BR:
			{
				self->pc = offset;
				break;
			}

			/* branch if stack top represents true */
			case BRTRUE:
			{
				a = POP();

				if (RedBool_IsTrue(a))
					self->pc = offset;

				Red_UNREF(a);
				break;
			}

			/* branch if stack top represents false */
			case BRFALSE:
			{
				a = POP();

				if (!RedBool_IsTrue(a))
					self->pc = offset;

				Red_UNREF(a);
				break;
			}

			/* wrap stack top with function */
			case MAKE_FUNCTION:
			{
				a = GET(0);
				r = RedFunction_WrapCode(a, frame, offset);

				if (!r)
				{
					DEC(1);
					goto exception;
				}

				SET(r);
				Red_UNREF(r);
				break;
			}

			/* check boundary of stack top */
			case RANGE:
			{
				a = GET(0);
				b = GET(1);
				c = GET(2);
				r = RedObject_Geq(c, b);

				if (!r)
				{
					DEC(3);
					goto exception;
				}

				if (RedBool_IsTrue(r))
				{
					Red_UNREF(r);
					r = RedObject_Leq(c, a);
				}

				if (!r)
				{
					DEC(3);
					goto exception;
				}

				DEC(2);
				SET(r);
				Red_UNREF(r);
				break;
			}

			/* load exception rescure block */
			case PUSH_BLOCK:
			{
				if (self->blocks.pb == self->blocks.capacity)
				{
					self->blocks.capacity <<= 1;
					self->blocks.ssp = RedMem_Realloc(self->blocks.ssp, sizeof(int) * self->blocks.capacity);
					self->blocks.blocks = RedMem_Realloc(self->blocks.blocks, sizeof(RedObject *) * self->blocks.capacity);
				}

				Red_REF(object);
				self->blocks.ssp[self->blocks.pb] = self->sp;
				self->blocks.blocks[self->blocks.pb++] = object;
				break;
			}

			/* expand sequence into stack */
			case EXPAND:
			{
				a = POP();

				if (RedArray_Exact(a))
				{
					for (int i = RedArray_Length(a) - 1; i >= 0; i--)
						PUSH(RedArray_Items(a)[i]);
				}
				else if (RedTuple_Exact(a))
				{
					for (int i = RedTuple_Length(a) - 1; i >= 0; i--)
						PUSH(RedTuple_Items(a)[i]);
				}
				else
				{
					Red_UNREF(a);
					RedException_ThrowFormat(&RedExc_TypeError, "Can only expand 'array' and 'tuple'");
					goto exception;
				}

				Red_UNREF(a);
				break;
			}

			/* start filling argument stack */
			case START_ARGS:
			{
				self->asp = self->sp;
				break;
			}

			/* invoke object as variable function */
			case INVOKE_VARG:
			{
				if (self->asp < 0)
				{
					RedException_ThrowFormat(&RedExc_InternalError, "INVOKE_VARG without START_ARG");
					goto exception;
				}

				/* need to exclude the function itself */
				int count = self->sp - self->asp - 1;

				a = GET(0);
				b = RedTuple_FromLength(count);
				self->asp = -1;

				for (int i = 0; i < count; i++)
				{
					RedTuple_Items(b)[i] = GET(i + 1);
					Red_REF(RedTuple_Items(b)[i]);
				}

				r = RedObject_Invoke(a, b);

				DEC(count);
				Red_UNREF(b);

				if (!r)
				{
					DEC(1);
					goto exception;
				}

				SET(r);
				Red_UNREF(r);
				break;
			}
		}

next_op:
		/* no error, goto next opcode */
		continue;

exception:
		/* get current exception, which ref-count was NOT increased
		 * if exception occured but no exception class was set, throw an internal error to parents
		 */
		if (!(exc = RedException_Exception()))
		{
			ATOMIC_DEC(recursive);
			RedException_ThrowFormat(&RedExc_InternalError, "Exception occured but no exceptions set");
			RedException_Rewind(frame);
			return NULL;
		}

		/* no exception recursion blocks were registered in this frame, throw to parent frame */
		if (self->blocks.pb == pb)
		{
			ATOMIC_DEC(recursive);
			RedException_Rewind(frame);
			return NULL;
		}

		int sp = self->blocks.ssp[--self->blocks.pb];
		RedObject *block = self->blocks.blocks[self->blocks.pb];

		/* search for matching exception rescue blocks */
		for (int i = 0; i < rarray_count(*RedBlock_Entries(block)); i++)
		{
			RedBlockEntry *entry = rarray_item_at(*RedBlock_Entries(block), i, RedBlockEntry);

			for (int j = 0; j < rarray_count(entry->e_types); j++)
			{
				/* resolve exception class */
				RedObject *name = rarray_item_at(entry->e_types, j, RedObject);
				RedTypeObject *exception = (RedTypeObject *)resolve_name(self, name);

				/* found, branch to this ERB */
				if (exception && RedType_IsInstance(exc, exception))
				{
					/* store exception to local scope */
					if (entry->e_name)
						RedMap_PutString(self->locals, entry->e_name, exc);

					/* rewind and branch */
					REWIND(sp);
					self->pc = entry->e_pc;

					/* continue execution */
					Red_UNREF(block);
					RedException_Clear();
					goto next_op;
				}
			}
		}

		/* no specified handler matched, try generic handler */
		if (RedBlock_Generic(block) >= 0)
		{
			/* rewind and branch */
			REWIND(sp);
			self->pc = RedBlock_Generic(block);

			/* continue execution */
			Red_UNREF(block);
			RedException_Clear();
			continue;
		}

		/* executes finally block if presents */
		if (RedBlock_Finally(block) >= 0)
		{
			/* rewind and branch */
			REWIND(sp);
			self->pc = RedBlock_Finally(block);

			/* continue execution */
			Red_UNREF(block);
			continue;
		}

		/* no handler matched, unwind stack and throw this exception to it's parent */
		RedException_Rewind(frame);
		Red_UNREF(block);
		ATOMIC_DEC(recursive);
		return NULL;
	}

#undef DEC
#undef GET
#undef POP
#undef SET
#undef PUSH

stack_corrupt:
	/* VM's internal stack was corrupted */
	fprintf(stderr, "*** FATAL: VM's internal stack was corrupted\n");
	abort();

internal_error:
	/* something that never happens happened */
	fprintf(stderr, "*** FATAL: something that never happens happened\n");
	abort();
}
